/***************************************************************************
 *   Copyright (c) 2015 Victor Titov (DeepSOIC) <vv.titov@gmail.com>       *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  is distributed in the hope that it will be useful,      *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/

#include "pch.h"
#include "Attacher.h"

#include <Base/Tools.h>
#include "Base/Console.h"
#include "App/Application.h"
#include <App/Document.h>
#include "TopoShape.h"
#include "Tools.h"
using namespace Part;
using namespace Attacher;

//These strings are for mode list enum property.
const char* AttachEngine::eMapModeStrings[] = {
    "Deactivated",
    "Translate",
    "ObjectXY",
    "ObjectXZ",
    "ObjectYZ",
    "FlatFace",
    "TangentPlane",
    "NormalToEdge",
    "FrenetNB",
    "FrenetTN",
    "FrenetTB",
    "Concentric",
    "SectionOfRevolution",
    "ThreePointsPlane",
    "ThreePointsNormal",
    "Folding",

    "ObjectX",
    "ObjectY",
    "ObjectZ",
    "AxisOfCurvature",
    "Directrix1",
    "Directrix2",
    "Asymptote1",
    "Asymptote2",
    "Tangent",
    "Normal",
    "Binormal",
    "TangentU",
    "TangentV",
    "TwoPointLine",
    "IntersectionLine",
    "ProximityLine",

    "ObjectOrigin",
    "Focus1",
    "Focus2",
    "OnEdge",
    "CenterOfCurvature",
    "CenterOfMass",
    "IntersectionPoint",
    "Vertex",
    "ProximityPoint1",
    "ProximityPoint2",

    "AxisOfInertia1",
    "AxisOfInertia2",
    "AxisOfInertia3",

    "InertialCS",

    "FaceNormal",

    "OZX",
    "OZY",
    "OXY",
    "OXZ",
    "OYZ",
    "OYX",

    nullptr
};

//this list must be in sync with eRefType enum.

const char* AttachEngine::eRefTypeStrings[] = {
    "Any",
    "Vertex",
    "Edge",
    "Face",

    "Line",
    "Curve",
    "Circle",
    "Conic",
    "Ellipse",
    "Parabola",
    "Hyperbola",

    "Plane",
    "Sphere",
    "Revolve",
    "Cylinder",
    "Torus",
    "Cone",

    "Object",
    "Solid",
    "Wire",
    nullptr
};


TYPESYSTEM_SOURCE_ABSTRACT(Attacher::AttachEngine, Base::BaseClass)

AttachEngine::AttachEngine()
    : mapMode(mmDeactivated), mapReverse(false), attachParameter(0.0),
      surfU(0.0), surfV(0.0) {}

void AttachEngine::setUp(
    eMapMode mapMode, bool mapReverse,
    double attachParameter,
    double surfU, double surfV,
    const Base::Placement& attachmentOffset) {
    this->mapMode = mapMode;
    this->mapReverse = mapReverse;
    this->attachParameter = attachParameter;
    this->surfU = surfU;
    this->surfV = surfV;
    this->attachmentOffset = attachmentOffset;
}

void AttachEngine::setUp(const AttachEngine& another) {
    setUp(
        another.mapMode,
        another.mapReverse,
        another.attachParameter,
        another.surfU,
        another.surfV,
        another.attachmentOffset);
}

Base::Placement AttachEngine::placementFactory(const gp_Dir& ZAxis,
                                               gp_Vec XAxis,
                                               gp_Pnt Origin,
                                               gp_Pnt refOrg,
                                               bool useRefOrg_Line,
                                               bool useRefOrg_Plane,
                                               bool makeYVertical,
                                               bool makeLegacyFlatFaceOrientation,
                                               Base::Placement* placeOfRef) const {
    if (useRefOrg_Line) {
        //move Origin to projection of refOrg onto ZAxis
        auto refOrgV = gp_Vec(refOrg.XYZ());
        auto OriginV = gp_Vec(Origin.XYZ());
        auto ZAxisV = gp_Vec(ZAxis);
        Origin = gp_Pnt((
            OriginV + ZAxisV * ZAxisV.Dot(refOrgV - OriginV)
        ).XYZ());
    }
    if (useRefOrg_Plane) {
        //move Origin to projection of refOrg onto plane (ZAxis, Origin)
        auto refOrgV = gp_Vec(refOrg.XYZ());
        auto OriginV = gp_Vec(Origin.XYZ());
        auto ZAxisV = gp_Vec(ZAxis);
        Origin = gp_Pnt((
            refOrgV + ZAxisV * ZAxisV.Dot(OriginV - refOrgV)
        ).XYZ());
    }

    if (XAxis.Magnitude() < Precision::Confusion())
        makeYVertical = true;

    gp_Ax3 ax3; //OCC representation of the final placement
    if (!makeYVertical) {
        ax3 = gp_Ax3(Origin, ZAxis, XAxis);
    } else if (!makeLegacyFlatFaceOrientation) {
        //align Y along Z, if possible
        gp_Vec YAxis(0.0, 0.0, 1.0);
        XAxis = YAxis.Crossed(gp_Vec(ZAxis));
        if (XAxis.Magnitude() < Precision::Confusion()) {
            //ZAxis is along true ZAxis
            XAxis = (gp_Vec(1, 0, 0) * ZAxis.Z()).Normalized();
        }
        ax3 = gp_Ax3(Origin, ZAxis, XAxis);
    } else if (makeLegacyFlatFaceOrientation) {
        //find out, to which axis of support Normal is closest to.
        //The result will be written into pos variable (0..2 = X..Z)
        if (!placeOfRef)
            throw std::string("AttachEngine::placementFactory: for Legacy mode, placement of the reference must be supplied. Got null instead!");
        Base::Placement& Place = *placeOfRef;
        Base::Vector3d dX, dY, dZ; //internal axes of support object, as they are in global space
        Place.getRotation().multVec(Base::Vector3d(1, 0, 0), dX);
        Place.getRotation().multVec(Base::Vector3d(0, 1, 0), dY);
        Place.getRotation().multVec(Base::Vector3d(0, 0, 1), dZ);
        gp_Dir dirX(dX.x, dX.y, dX.z);
        gp_Dir dirY(dY.x, dY.y, dY.z);
        gp_Dir dirZ(dZ.x, dZ.y, dZ.z);
        double cosNX = ZAxis.Dot(dirX);
        double cosNY = ZAxis.Dot(dirY);
        double cosNZ = ZAxis.Dot(dirZ);
        std::vector<double> cosXYZ;
        cosXYZ.push_back(fabs(cosNX));
        cosXYZ.push_back(fabs(cosNY));
        cosXYZ.push_back(fabs(cosNZ));

        int pos = std::max_element(cosXYZ.begin(), cosXYZ.end()) - cosXYZ.begin();

        // +X/-X
        if (pos == 0) {
            if (cosNX > 0)
                ax3 = gp_Ax3(Origin, ZAxis, dirY);
            else
                ax3 = gp_Ax3(Origin, ZAxis, -dirY);
        }
        // +Y/-Y
        else if (pos == 1) {
            if (cosNY > 0)
                ax3 = gp_Ax3(Origin, ZAxis, -dirX);
            else
                ax3 = gp_Ax3(Origin, ZAxis, dirX);
        }
        // +Z/-Z
        else {
            ax3 = gp_Ax3(Origin, ZAxis, dirX);
        }
    }

    if (this->mapReverse) {
        ax3.ZReverse();
        ax3.XReverse();
    }

    //convert ax3 into Base::Placement
    gp_Trsf Trf;
    Trf.SetTransformation(ax3);
    Trf.Invert();
    Trf.SetScaleFactor(1.0);

    Base::Matrix4D mtrx;
    TopoShape::convertToMatrix(Trf, mtrx);

    return Base::Placement(mtrx);
}

void AttachEngine::suggestMapModes(SuggestResult& result) const {
    std::vector<eMapMode>& mlist = result.allApplicableModes;
    mlist.clear();
    mlist.reserve(mmDummy_NumberOfModes);

    std::set<eRefType>& hints = result.nextRefTypeHint;
    hints.clear();

    std::map<eMapMode, refTypeStringList>& mlist_reachable = result.reachableModes;
    mlist_reachable.clear();

    result.message = SuggestResult::srLinkBroken;
    result.bestFitMode = mmDeactivated;


    std::vector<const TopoDS_Shape*> shapes;
    std::vector<TopoDS_Shape> shapeStorage;
    std::vector<eRefType> typeStr;
    try {
        readLinks(shapes, shapeStorage, typeStr);
    } catch (std::exception& err) {
        result.references_Types = typeStr;
        result.message = SuggestResult::srLinkBroken;
        return;
    }

    result.references_Types = typeStr;

    //search valid modes.
    int bestMatchScore = -1;
    result.message = SuggestResult::srNoModesFit;
    for (std::size_t iMode = 0; iMode < this->modeRefTypes.size(); ++iMode) {
        if (!this->modeEnabled[iMode])
            continue;
        const refTypeStringList& listStrings = modeRefTypes[iMode];
        for (std::size_t iStr = 0; iStr < listStrings.size(); ++iStr) {
            int score = 1; //-1 = topo incompatible, 0 = topo compatible, geom incompatible; 1+ = compatible (the higher - the more specific is the mode for the support)
            const refTypeString& str = listStrings[iStr];
            for (std::size_t iChr = 0; iChr < str.size() && iChr < typeStr.size(); ++iChr) {
                int match = isShapeOfType(typeStr[iChr], str[iChr]);
                switch (match) {
                case -1:
                    score = -1;
                    break;
                case 0:
                    score = 0;
                    break;
                case 1:
                    //keep score
                    break;
                default: //2 and above
                    if (score > 0)
                        score += match;
                    break;
                }
            }

            if (score > 0 && str.size() > typeStr.size()) {
                //mode does not fit, but adding more references will make this mode fit.
                hints.insert(str[typeStr.size()]);

                //build string of references to be added to fit this mode
                refTypeString extraRefs;
                extraRefs.resize(str.size() - typeStr.size());
                for (std::size_t iChr = typeStr.size(); iChr < str.size(); iChr++) {
                    extraRefs[iChr - typeStr.size()] = str[iChr];
                }

                //add reachable mode
                auto it_r = mlist_reachable.find(static_cast<eMapMode>(iMode));
                if (it_r == mlist_reachable.end()) {
                    it_r = mlist_reachable.insert(std::pair<eMapMode, refTypeStringList>(static_cast<eMapMode>(iMode), refTypeStringList())).first;
                }
                refTypeStringList& list = it_r->second;
                list.push_back(extraRefs);
            }

            //size check is last, because we needed to collect hints
            if (str.size() != typeStr.size())
                score = -1;

            if (score > -1) {
                //still output a best match, even if it is not completely compatible
                if (score > bestMatchScore) {
                    bestMatchScore = score;
                    result.bestFitMode = static_cast<eMapMode>(iMode);
                    result.message = score > 0 ? SuggestResult::srOK : SuggestResult::srIncompatibleGeometry;
                }
            }
            if (score > 0) {
                if (mlist.size() == 0)
                    mlist.push_back(static_cast<eMapMode>(iMode));
                else if (mlist.back() != static_cast<eMapMode>(iMode))
                    mlist.push_back(static_cast<eMapMode>(iMode));
            }
        }
    }
}

void AttachEngine::EnableAllSupportedModes() {
    this->modeEnabled.resize(mmDummy_NumberOfModes, false);
    assert(modeRefTypes.size() > 0);
    for (std::size_t i = 0; i < this->modeEnabled.size(); i++) {
        modeEnabled[i] = modeRefTypes[i].size() > 0;
    }
}

eRefType AttachEngine::getShapeType(const TopoDS_Shape& sh) {
    if (sh.IsNull())
        return rtAnything;

    switch (sh.ShapeType()) {
    case TopAbs_SHAPE:
        return rtAnything; //note: there's no rtPart detection here - not enough data!
        break;
    case TopAbs_SOLID:
        return rtSolid;
        break;
    case TopAbs_COMPOUND: {
        const TopoDS_Compound& cmpd = TopoDS::Compound(sh);
        TopoDS_Iterator it(cmpd, Standard_False, Standard_False); //don't mess with placements, to hopefully increase speed
        if (!it.More()) //empty compound
            return rtAnything;
        const TopoDS_Shape& sh1 = it.Value();
        it.Next();
        if (it.More()) {
            //more than one object, a true compound
            return rtAnything;
        }
        //just one object, let's take a look inside
        return getShapeType(sh1);
    }
    break;
    case TopAbs_COMPSOLID:
    case TopAbs_SHELL:
        return rtAnything;
        break;
    case TopAbs_FACE: {
        const TopoDS_Face& f = TopoDS::Face(sh);
        BRepAdaptor_Surface surf(f, /*restriction=*/Standard_False);
        switch (surf.GetType()) {
        case GeomAbs_Plane:
            return rtFlatFace;
        case GeomAbs_Cylinder:
            return rtCylindricalFace;
        case GeomAbs_Cone:
            return rtConicalFace;
        case GeomAbs_Sphere:
            return rtSphericalFace;
        case GeomAbs_Torus:
            return rtToroidalFace;
        case GeomAbs_BezierSurface:
            break;
        case GeomAbs_BSplineSurface:
            break;
        case GeomAbs_SurfaceOfRevolution:
            return rtSurfaceRev;
        case GeomAbs_SurfaceOfExtrusion:
            break;
        case GeomAbs_OffsetSurface:
            break;
        case GeomAbs_OtherSurface:
            break;
        }
        return rtFace;
    }
    break;
    case TopAbs_EDGE: {
        const TopoDS_Edge& e = TopoDS::Edge(sh);
        BRepAdaptor_Curve crv(e);
        switch (crv.GetType()) {
        case GeomAbs_Line:
            return rtLine;
        case GeomAbs_Circle:
            return rtCircle;
        case GeomAbs_Ellipse:
            return rtEllipse;
        case GeomAbs_Hyperbola:
            return rtHyperbola;
        case GeomAbs_Parabola:
            return rtParabola;
        case GeomAbs_BezierCurve:
        case GeomAbs_BSplineCurve:
        case GeomAbs_OtherCurve:
#if OCC_VERSION_HEX >= 0x070000
        case GeomAbs_OffsetCurve:
#endif
            return rtCurve;
        }
    }
    break;
    case TopAbs_WIRE:
        return rtWire;
    case TopAbs_VERTEX:
        return rtVertex;
    default:
        throw std::string("AttachEngine::getShapeType: unexpected TopoDS_Shape::ShapeType");
    } //switch shapetype
    return rtAnything; //shouldn't happen, it's here to shut up compiler warning
}

eRefType AttachEngine::getShapeType(const Sketcher::SketchObject* obj, const std::string& subshape) {
    std::vector<const TopoDS_Shape*> shapes;
    std::vector<TopoDS_Shape> copiedShapeStorage;
    std::vector<eRefType> types;

    assert(types.size() == 1);
    return types[0];
}

eRefType AttachEngine::downgradeType(eRefType type) {
    //get rid of hasplacement flags, to simplify the rest
    type = static_cast<eRefType>(type & (rtFlagHasPlacement - 1));
    //FIXME: reintroduce the flag when returning a value.

    switch (type) {
    case rtVertex:
    case rtEdge:
    case rtFace:
        return rtAnything;
    case rtAnything:
        return rtAnything;
    case rtLine:
    case rtCurve:
        return rtEdge;
    case rtConic:
    case rtCircle:
        return rtCurve;
    case rtEllipse:
    case rtParabola:
    case rtHyperbola:
        return rtConic;
    case rtFlatFace:
    case rtSphericalFace:
    case rtSurfaceRev:
        return rtFace;
    case rtCylindricalFace:
    case rtToroidalFace:
    case rtConicalFace:
        return rtSurfaceRev;
    case rtSolid:
    case rtWire:
        return rtPart;
    case rtPart:
        return rtAnything;
    default:
        throw std::string("AttachEngine::downgradeType: unknown type");
    }
}

int AttachEngine::getTypeRank(eRefType type) {
    //get rid of hasplacement flags, to simplify the rest
    type = static_cast<eRefType>(type & (rtFlagHasPlacement - 1));

    int rank = 0;
    while (type != rtAnything) {
        type = downgradeType(type);
        rank++;
        assert(rank<8); //downgrading never yields rtAnything, something's wrong with downgrader.
    }
    return rank;
}

int AttachEngine::isShapeOfType(eRefType shapeType, eRefType requirement) {
    //first up, check for hasplacement flag
    if (requirement & rtFlagHasPlacement) {
        if (!(shapeType & rtFlagHasPlacement))
            return -1;
    }

    //get rid of hasplacement flags, to simplify the rest
    shapeType = static_cast<eRefType>(shapeType & (rtFlagHasPlacement - 1));
    requirement = static_cast<eRefType>(requirement & (rtFlagHasPlacement - 1));

    if (requirement == rtAnything)
        return 1;

    int reqRank = getTypeRank(requirement);

    //test for valid match
    eRefType shDeg = shapeType;
    while (shDeg != rtAnything) {
        if (shDeg == requirement)
            return reqRank;
        shDeg = downgradeType(shDeg);
    }

    //test for slightly invalid match (e.g. requirement==line, shapeType == curve)
    requirement = downgradeType(requirement);
    if (requirement != rtAnything) {
        eRefType shDeg = shapeType;
        while (shDeg != rtAnything) {
            if (shDeg == requirement)
                return 0;
            shDeg = downgradeType(shDeg);
        }
    }

    //complete mismatch!
    return -1;
}

std::string AttachEngine::getModeName(eMapMode mmode) {
    if (mmode < 0 || mmode >= mmDummy_NumberOfModes)
        throw std::string("AttachEngine::getModeName: Attachment Mode index is out of range");
    return std::string(eMapModeStrings[mmode]);
}

eMapMode AttachEngine::getModeByName(const std::string& modeName) {
    for (int mmode = 0; mmode < mmDummy_NumberOfModes; mmode++) {
        if (strcmp(eMapModeStrings[mmode], modeName.c_str()) == 0) {
            return static_cast<eMapMode>(mmode);
        }
    }
    std::stringstream errMsg;
    errMsg << "AttachEngine::getModeByName: mode with this name doesn't exist: " << modeName;
    throw std::string(errMsg.str());
}

std::string AttachEngine::getRefTypeName(eRefType shapeType) {
    auto flagless = static_cast<eRefType>(shapeType & 0xFF);
    if (flagless < 0 || flagless >= rtDummy_numberOfShapeTypes)
        throw std::string("eRefType value is out of range");
    auto result = std::string(eRefTypeStrings[flagless]);
    if (shapeType & rtFlagHasPlacement) {
        result.append("|Placement");
    }
    return result;
}

eRefType AttachEngine::getRefTypeByName(const std::string& typeName) {
    std::string flagless;
    std::string flags;
    size_t seppos = typeName.find('|');
    flagless = typeName.substr(0, seppos);
    if (seppos != std::string::npos) {
        flags = typeName.substr(seppos + 1);
    }
    for (int irt = 0; irt < rtDummy_numberOfShapeTypes; irt++) {
        if (strcmp(flagless.c_str(), eRefTypeStrings[irt]) == 0) {
            if (strcmp("Placement", flags.c_str()) == 0) {
                return static_cast<eRefType>(irt | rtFlagHasPlacement);
            }
            if (flags.length() == 0) {
                return static_cast<eRefType>(irt);
            }
            std::stringstream errmsg;
            errmsg << "RefType flag not recognized: " << flags;
            throw std::string(errmsg.str());
        }
    }
    std::stringstream errmsg;
    errmsg << "RefType not recognized: " << typeName;
    throw std::string(errmsg.str());
}

GProp_GProps AttachEngine::getInertialPropsOfShape(const std::vector<const TopoDS_Shape*>& shapes) {
    //explode compounds
    TopTools_HSequenceOfShape totalSeq;
    for (const TopoDS_Shape* pSh : shapes) {
        ShapeExtend_Explorer xp;
        totalSeq.Append(xp.SeqFromCompound(*pSh, /*recursive=*/true));
    }
    if (totalSeq.Length() == 0)
        throw std::string("AttachEngine::getInertialPropsOfShape: no geometry provided");
    const TopoDS_Shape& sh0 = totalSeq.Value(1);
    switch (sh0.ShapeType()) {
    case TopAbs_VERTEX: {
        GProp_PGProps gpr;
        for (int i = 0; i < totalSeq.Length(); i++) {
            const TopoDS_Shape& sh = totalSeq.Value(i + 1);
            if (sh.ShapeType() != TopAbs_VERTEX)
                throw std::string("AttachEngine::getInertialPropsOfShape: provided shapes are incompatible (not only vertices)");
            gpr.AddPoint(BRep_Tool::Pnt(TopoDS::Vertex(sh)));
        }
        return gpr;
    }
    break;
    case TopAbs_EDGE:
    case TopAbs_WIRE: {
        GProp_GProps gpr_acc;
        GProp_GProps gpr;
        for (int i = 0; i < totalSeq.Length(); i++) {
            const TopoDS_Shape& sh = totalSeq.Value(i + 1);
            if (sh.ShapeType() != TopAbs_EDGE && sh.ShapeType() != TopAbs_WIRE)
                throw std::string("AttachEngine::getInertialPropsOfShape: provided shapes are incompatible (not only edges/wires)");
            if (sh.Infinite())
                throw std::string("AttachEngine::getInertialPropsOfShape: infinite shape provided");
            BRepGProp::LinearProperties(sh, gpr);
            gpr_acc.Add(gpr);
        }
        return gpr_acc;
    }
    break;
    case TopAbs_FACE:
    case TopAbs_SHELL: {
        GProp_GProps gpr_acc;
        GProp_GProps gpr;
        for (int i = 0; i < totalSeq.Length(); i++) {
            const TopoDS_Shape& sh = totalSeq.Value(i + 1);
            if (sh.ShapeType() != TopAbs_FACE && sh.ShapeType() != TopAbs_SHELL)
                throw std::string("AttachEngine::getInertialPropsOfShape: provided shapes are incompatible (not only faces/shells)");
            if (sh.Infinite())
                throw std::string("AttachEngine::getInertialPropsOfShape: infinite shape provided");
            BRepGProp::SurfaceProperties(sh, gpr);
            gpr_acc.Add(gpr);
        }
        return gpr_acc;
    }
    break;
    case TopAbs_SOLID:
    case TopAbs_COMPSOLID: {
        GProp_GProps gpr_acc;
        GProp_GProps gpr;
        for (int i = 0; i < totalSeq.Length(); i++) {
            const TopoDS_Shape& sh = totalSeq.Value(i + 1);
            if (sh.ShapeType() != TopAbs_SOLID && sh.ShapeType() != TopAbs_COMPSOLID)
                throw std::string("AttachEngine::getInertialPropsOfShape: provided shapes are incompatible (not only solids/compsolids)");
            if (sh.Infinite())
                throw std::string("AttachEngine::getInertialPropsOfShape: infinite shape provided");
            BRepGProp::VolumeProperties(sh, gpr);
            gpr_acc.Add(gpr);
        }
        return gpr_acc;
    }
    break;
    default:
        throw std::string("AttachEngine::getInertialPropsOfShape: unexpected shape type");
    }
}


void AttachEngine::throwWrongMode(eMapMode mmode) {
    std::stringstream errmsg;
    if (mmode >= 0 && mmode < mmDummy_NumberOfModes) {
        if (eMapModeStrings[mmode]) {
            errmsg << "Attachment mode " << eMapModeStrings[mmode] << " is not implemented.";
        } else {
            errmsg << "Attachment mode " << mmode << " is undefined.";
        }
    } else {
        errmsg << "Attachment mode index (" << mmode << ") is out of range.";
    }
    throw std::exception(errmsg.str().c_str());
}


//=================================================================================

TYPESYSTEM_SOURCE(Attacher::AttachEngine3D, Attacher::AttachEngine)

AttachEngine3D::AttachEngine3D() {
    //fill type lists for modes
    modeRefTypes.resize(mmDummy_NumberOfModes);
    refTypeString s;
    refTypeStringList ss;

    modeRefTypes[mmTranslate].push_back(cat(rtVertex));

    ss.clear();
    ss.push_back(cat(static_cast<eRefType>(rtAnything | rtFlagHasPlacement)));
    ss.push_back(cat(rtConic));
    modeRefTypes[mmObjectXY] = ss;
    modeRefTypes[mmObjectXZ] = ss;
    modeRefTypes[mmObjectYZ] = ss;

    modeRefTypes[mmInertialCS].push_back(cat(rtAnything));
    modeRefTypes[mmInertialCS].push_back(cat(rtAnything, rtAnything));
    modeRefTypes[mmInertialCS].push_back(cat(rtAnything, rtAnything, rtAnything));
    modeRefTypes[mmInertialCS].push_back(cat(rtAnything, rtAnything, rtAnything, rtAnything));

    modeRefTypes[mmFlatFace].push_back(cat(rtFlatFace));

    modeRefTypes[mmTangentPlane].push_back(cat(rtFace, rtVertex));
    modeRefTypes[mmTangentPlane].push_back(cat(rtVertex, rtFace));

    //---------Edge-driven

    s = cat(rtEdge);
    modeRefTypes[mmNormalToPath].push_back(s);

    s = cat(rtCurve);
    modeRefTypes[mmFrenetNB].push_back(s);
    modeRefTypes[mmFrenetTN].push_back(s);
    modeRefTypes[mmFrenetTB].push_back(s);
    modeRefTypes[mmRevolutionSection].push_back(s);
    modeRefTypes[mmConcentric].push_back(s);
    s = cat(rtCircle);
    modeRefTypes[mmRevolutionSection].push_back(s); //for this mode to get best score on circles
    modeRefTypes[mmConcentric].push_back(s);

    //-----------Edge-driven at vertex

    s = cat(rtEdge, rtVertex);
    modeRefTypes[mmNormalToPath].push_back(s);
    s = cat(rtVertex, rtEdge);
    modeRefTypes[mmNormalToPath].push_back(s);

    s = cat(rtCurve, rtVertex);
    modeRefTypes[mmFrenetNB].push_back(s);
    modeRefTypes[mmFrenetTN].push_back(s);
    modeRefTypes[mmFrenetTB].push_back(s);
    modeRefTypes[mmRevolutionSection].push_back(s);
    modeRefTypes[mmConcentric].push_back(s);
    s = cat(rtCircle, rtVertex);
    modeRefTypes[mmRevolutionSection].push_back(s); //for this mode to get best score on circles
    modeRefTypes[mmConcentric].push_back(s);

    s = cat(rtVertex, rtCurve);
    modeRefTypes[mmFrenetNB].push_back(s);
    modeRefTypes[mmFrenetTN].push_back(s);
    modeRefTypes[mmFrenetTB].push_back(s);
    modeRefTypes[mmRevolutionSection].push_back(s);
    modeRefTypes[mmConcentric].push_back(s);
    s = cat(rtVertex, rtCircle);
    modeRefTypes[mmRevolutionSection].push_back(s); //for this mode to get best score on circles
    modeRefTypes[mmConcentric].push_back(s);

    //------------ThreePoints

    s = cat(rtVertex, rtVertex, rtVertex);
    modeRefTypes[mmThreePointsPlane].push_back(s);
    modeRefTypes[mmThreePointsNormal].push_back(s);

    s = cat(rtLine, rtVertex);
    modeRefTypes[mmThreePointsPlane].push_back(s);
    modeRefTypes[mmThreePointsNormal].push_back(s);

    s = cat(rtVertex, rtLine);
    modeRefTypes[mmThreePointsPlane].push_back(s);
    modeRefTypes[mmThreePointsNormal].push_back(s);

    s = cat(rtLine, rtLine);
    modeRefTypes[mmThreePointsPlane].push_back(s);
    modeRefTypes[mmThreePointsNormal].push_back(s);

    //------------origin-axis-axis modes
    for (int mmode = mmOZX; mmode <= mmOYX; ++mmode) {
        modeRefTypes[mmode].push_back(cat(rtVertex, rtVertex, rtVertex));
        modeRefTypes[mmode].push_back(cat(rtVertex, rtVertex, rtLine));
        modeRefTypes[mmode].push_back(cat(rtVertex, rtLine, rtVertex));
        modeRefTypes[mmode].push_back(cat(rtVertex, rtLine, rtLine));
        modeRefTypes[mmode].push_back(cat(rtVertex, rtVertex));
        modeRefTypes[mmode].push_back(cat(rtVertex, rtLine));
    }


    modeRefTypes[mmFolding].push_back(cat(rtLine, rtLine, rtLine, rtLine));

    this->EnableAllSupportedModes();
}

AttachEngine3D* AttachEngine3D::copy() const {
    auto p = new AttachEngine3D;
    p->setUp(*this);
    return p;
}

Base::Placement* AttachEngine3D::calculateAttachedPlacement(const Base::Placement& origPlacement) const { return nullptr; }

double AttachEngine3D::calculateFoldAngle(gp_Vec axA, gp_Vec axB, gp_Vec edA, gp_Vec edB) const {
    //DeepSOIC: this hardcore math can probably be replaced with a couple of
    //clever OCC calls... See forum thread "Sketch mapping enhancement" for a
    //picture on how this math was derived.
    //http://forum.freecadweb.org/viewtopic.php?f=8&t=10511&sid=007946a934530ff2a6c9259fb32624ec&start=40#p87584
    axA.Normalize();
    axB.Normalize();
    edA.Normalize();
    edB.Normalize();
    gp_Vec norm = axA.Crossed(axB);
    if (norm.Magnitude() < Precision::Confusion())
        throw std::string("calculateFoldAngle: Folding axes are parallel, folding angle cannot be computed.");
    norm.Normalize();
    double a = edA.Dot(axA);
    double ra = edA.Crossed(axA).Magnitude();
    if (fabs(ra) < Precision::Confusion())
        throw std::string("calculateFoldAngle: axisA and edgeA are parallel, folding can't be computed.");
    double b = edB.Dot(axB);
    double costheta = axB.Dot(axA);
    double sintheta = axA.Crossed(axB).Dot(norm);
    double singama = -costheta;
    double cosgama = sintheta;
    double k = b * cosgama;
    double l = a + b * singama;
    double xa = k + l * singama / cosgama;
    double cos_unfold = -xa / ra;
    if (fabs(cos_unfold) > 0.999)
        throw std::string("calculateFoldAngle: cosine of folding angle is too close to or above 1.");
    return acos(cos_unfold);
}


//=================================================================================

TYPESYSTEM_SOURCE(Attacher::AttachEnginePlane, Attacher::AttachEngine)

AttachEnginePlane::AttachEnginePlane() {
    //re-used 3d modes: all of Attacher3d
    AttachEngine3D attacher3D;
    this->modeRefTypes = attacher3D.modeRefTypes;
    this->EnableAllSupportedModes();
}

AttachEnginePlane* AttachEnginePlane::copy() const {
    auto p = new AttachEnginePlane;
    p->setUp(*this);
    return p;
}

Base::Placement* AttachEnginePlane::calculateAttachedPlacement(const Base::Placement& origPlacement) const {
    return nullptr;
}

//=================================================================================

TYPESYSTEM_SOURCE(Attacher::AttachEngineLine, Attacher::AttachEngine)

AttachEngineLine::AttachEngineLine() {
    //fill type lists for modes
    modeRefTypes.resize(mmDummy_NumberOfModes);
    refTypeString s;

    //re-used 3d modes
    AttachEngine3D attacher3D;
    modeRefTypes[mm1AxisX] = attacher3D.modeRefTypes[mmObjectYZ];
    modeRefTypes[mm1AxisY] = attacher3D.modeRefTypes[mmObjectXZ];
    modeRefTypes[mm1AxisZ] = attacher3D.modeRefTypes[mmObjectXY];
    modeRefTypes[mm1AxisCurv] = attacher3D.modeRefTypes[mmRevolutionSection];
    modeRefTypes[mm1Binormal] = attacher3D.modeRefTypes[mmFrenetTN];
    modeRefTypes[mm1Normal] = attacher3D.modeRefTypes[mmFrenetTB];
    modeRefTypes[mm1Tangent] = attacher3D.modeRefTypes[mmNormalToPath];

    modeRefTypes[mm1TwoPoints].push_back(cat(rtVertex, rtVertex));
    modeRefTypes[mm1TwoPoints].push_back(cat(rtLine));

    modeRefTypes[mm1Asymptote1].push_back(cat(rtHyperbola));
    modeRefTypes[mm1Asymptote2].push_back(cat(rtHyperbola));

    modeRefTypes[mm1Directrix1].push_back(cat(rtConic));

    modeRefTypes[mm1Directrix2].push_back(cat(rtEllipse));
    modeRefTypes[mm1Directrix2].push_back(cat(rtHyperbola));

    modeRefTypes[mm1Proximity].push_back(cat(rtAnything, rtAnything));

    modeRefTypes[mm1AxisInertia1].push_back(cat(rtAnything));
    modeRefTypes[mm1AxisInertia1].push_back(cat(rtAnything, rtAnything));
    modeRefTypes[mm1AxisInertia1].push_back(cat(rtAnything, rtAnything, rtAnything));
    modeRefTypes[mm1AxisInertia1].push_back(cat(rtAnything, rtAnything, rtAnything, rtAnything));
    modeRefTypes[mm1AxisInertia2] = modeRefTypes[mm1AxisInertia1];
    modeRefTypes[mm1AxisInertia3] = modeRefTypes[mm1AxisInertia1];

    modeRefTypes[mm1FaceNormal] = attacher3D.modeRefTypes[mmTangentPlane];


    this->EnableAllSupportedModes();
}

AttachEngineLine* AttachEngineLine::copy() const {
    auto p = new AttachEngineLine;
    p->setUp(*this);
    return p;
}

Base::Placement* AttachEngineLine::calculateAttachedPlacement(const Base::Placement& origPlacement) const { return nullptr; }


//=================================================================================

TYPESYSTEM_SOURCE(Attacher::AttachEnginePoint, Attacher::AttachEngine)

AttachEnginePoint::AttachEnginePoint() {
    //fill type lists for modes
    modeRefTypes.resize(mmDummy_NumberOfModes);
    refTypeString s;

    //re-used 3d modes
    AttachEngine3D attacher3D;
    modeRefTypes[mm0Origin] = attacher3D.modeRefTypes[mmObjectXY];
    modeRefTypes[mm0CenterOfCurvature] = attacher3D.modeRefTypes[mmRevolutionSection];
    modeRefTypes[mm0OnEdge] = attacher3D.modeRefTypes[mmNormalToPath];

    modeRefTypes[mm0Vertex].push_back(cat(rtVertex));
    modeRefTypes[mm0Vertex].push_back(cat(rtLine));

    modeRefTypes[mm0Focus1].push_back(cat(rtConic));

    modeRefTypes[mm0Focus2].push_back(cat(rtEllipse));
    modeRefTypes[mm0Focus2].push_back(cat(rtHyperbola));

    s = cat(rtAnything, rtAnything);
    modeRefTypes[mm0ProximityPoint1].push_back(s);
    modeRefTypes[mm0ProximityPoint2].push_back(s);

    modeRefTypes[mm0CenterOfMass].push_back(cat(rtAnything));
    modeRefTypes[mm0CenterOfMass].push_back(cat(rtAnything, rtAnything));
    modeRefTypes[mm0CenterOfMass].push_back(cat(rtAnything, rtAnything, rtAnything));
    modeRefTypes[mm0CenterOfMass].push_back(cat(rtAnything, rtAnything, rtAnything, rtAnything));

    this->EnableAllSupportedModes();
}

AttachEnginePoint* AttachEnginePoint::copy() const {
    auto p = new AttachEnginePoint;
    p->setUp(*this);
    return p;
}

Base::Placement* AttachEnginePoint::calculateAttachedPlacement(const Base::Placement& origPlacement) const { return nullptr; }

gp_Pnt AttachEnginePoint::getProximityPoint(eMapMode mmode, const TopoDS_Shape& s1, const TopoDS_Shape& s2) const {
    // #0003921: Crash when opening document with datum point intersecting line and plane
    //
    // BRepExtrema_DistanceSS is used inside BRepExtrema_DistShapeShape and can cause
    // a crash if the input shape is an unlimited face.
    // So, when the input is a face and an edge then before checking for minimum distances
    // try to determine intersection points.
    try {
        TopoDS_Shape face, edge;
        if (s1.ShapeType() == TopAbs_FACE &&
            s2.ShapeType() == TopAbs_EDGE) {
            face = s1;
            edge = s2;
        } else if (s1.ShapeType() == TopAbs_EDGE &&
            s2.ShapeType() == TopAbs_FACE) {
            edge = s1;
            face = s2;
        }

        // edge and face
        if (!edge.IsNull() && !face.IsNull()) {
            BRepAdaptor_Curve crv(TopoDS::Edge(edge));

            GeomAdaptor_Curve typedcrv;
            try {
                // Important note about BRepIntCurveSurface_Inter and GeomAdaptor_Curve
                //
                // A GeomAdaptor_Curve obtained directly from BRepAdaptor_Curve will lose the information
                // about Location/orientation of the edge.
                //
                // That's why GeomAdaptor::MakeCurve() is used to create a new geometry with the
                // transformation applied.
                typedcrv.Load(GeomAdaptor::MakeCurve(crv));
            } catch (const Standard_DomainError&) {
                Handle(Geom_Curve) curve = crv.Curve().Curve();
                if (curve.IsNull()) {
                    // Can this ever happen?
                    typedcrv = crv.Curve();
                } else {
                    curve = Handle(Geom_Curve)::DownCast(curve->Copy());
                    curve->Transform(crv.Trsf());
                    typedcrv.Load(curve);
                }
            }

            BRepIntCurveSurface_Inter intCS;
            intCS.Init(face, typedcrv, Precision::Confusion());
            std::vector<gp_Pnt> points;
            for (; intCS.More(); intCS.Next()) {
                gp_Pnt pnt = intCS.Pnt();
                points.push_back(pnt);
            }

            if (points.size() > 1)
                Base::Console().Warning("AttachEnginePoint::calculateAttachedPlacement: proximity calculation gave %d solutions, ambiguous.\n", static_cast<int>(points.size()));

            // if an intersection is found return the first hit
            // otherwise continue with BRepExtrema_DistShapeShape
            if (!points.empty())
                return points.front();
        }
    } catch (const Standard_Failure&) {
        // ignore
    }

    BRepExtrema_DistShapeShape distancer(s1, s2);
    if (!distancer.IsDone())
        throw std::string("AttachEnginePoint::calculateAttachedPlacement: proximity calculation failed.");
    if (distancer.NbSolution() > 1)
        Base::Console().Warning("AttachEnginePoint::calculateAttachedPlacement: proximity calculation gave %i solutions, ambiguous.\n", distancer.NbSolution());

    gp_Pnt p1 = distancer.PointOnShape1(1);
    gp_Pnt p2 = distancer.PointOnShape2(1);
    if (mmode == mm0ProximityPoint1)
        return p1;
    return p2;
}
